home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / conglom < prev    next >
Encoding:
Korn shell script  |  1997-08-26  |  12.3 KB  |  422 lines

  1. #!/bin/ksh
  2. # @(#) conglom.ksh 1.1 96/06/20
  3. # 93/11/23 John H. DuBois III (john@armory.com)
  4. # 93/11/26 If no match, mention almost-matching files.
  5. # 94/11/22 Exit with number of failures.  Added ln options.
  6. #          Do select if multiple matches found in database.
  7. # 95/08/09 Skip any file found in database if it isn't a regular file or
  8. #          doesn't exist anymore.
  9. # 96/06/20 Do uncompression as neccessary.  Added tDf options.  Read rcfile.
  10. #          Removed CGFILES environment variable.
  11.  
  12. l_name=${0##*/}
  13. Usage="Usage: $l_name [-bdDhlstv] filename [filename ...] [destination]"
  14.  
  15. alias istrue="test 0 -ne"
  16. alias isfalse="test 0 -eq"
  17.  
  18. typeset -i backup=0 remove=1 verbose=0 database=0 list=0 interactive=0
  19. test=false
  20. destLookup=false
  21. [ -t 0 ] && interactive=1
  22. rcFile=.conglom
  23. db=.files
  24.  
  25. while getopts :bdDhlstvn opt; do
  26.     case $opt in
  27.     h)
  28.     print -r -- \
  29. "$l_name conglomerate files.
  30. $l_name appends the contents of files to other already existing files and then
  31. removes the files whose contents were appended.
  32. $Usage
  33. If the destination is a directory, the contents of each file is appended to a
  34. file with the same name in the directory.  If the directory does not exist or a
  35. file with the same name does not exist in the directory, the operation fails
  36. and the source file is not removed.  The destination can be forced to be a
  37. directory by ending it with a '/'.  If the destination is a file, the contents
  38. of all of the other files is appended to it.  If the destination does not
  39. exist, the operation fails and the source files are not removed.
  40. In all cases, if a source filename ends with a compression suffix (.Z, .z, and
  41. .gz), the suffix is removed before searching for a match, and the file is
  42. uncompressed before its contents are appended to the destination file.
  43. If only one filename is given, a file database is used to find the destination.
  44. The last component of the filename is searched for in the database; if one and
  45. only one name is found that ends with that component (other than a name that
  46. refers to the source file itself), it is used as the destination.   If more
  47. thank one matching name is found and the standard input is a terminal, the
  48. matching filenames are printed to be selected from.  If no match is found, an
  49. error message is printed.  Relative filenames in the database are taken to be
  50. relative to the user's home directory.  The default database is the file
  51. \"$db\" in the user's home directory.  Any file named which is not
  52. successfully appended and (if -s is not given) removed is considered to have
  53. failed. $l_name exits with the number of failures, or 255 if more than 255
  54. conglomerations failed.
  55. Options:
  56. Some of the following options can be set in a configuration file named
  57. \"$rcFile\", which may be in the invoking user's home directory or in the
  58. directory specified by the environment variable UHOME (if both exist,
  59. assignments in the former take precedence).  In this file, values are assigned
  60. to variables in shell style, e.g. \"VARNAME=value\".  To set an option that
  61. does not take a value, use \"VARNAME=1\".  Options given in the configuration
  62. files are overridden by those given on the command line.  Variable names are
  63. given in parentheses following option descriptions.
  64. -b: A backup of each destination file is made; the name is its original name
  65.     with a '-' attached.  Any file that exists with that name is overwritten.
  66.     (BACKUP)
  67. -f<database>: Use <database> instead of the default file database.  (DATABASE)
  68. -d: Use the file database to find the destination for each source file.
  69.     All arguments are taken to be source files.  
  70. -D: If more than one argument is given and the last filename is a simple
  71.     filename without a directory component (it contains no '/' characters), the
  72.     last argument is taken to be a destination file, and is looked up in the
  73.     database.  (By default, if more than one filename is given the last name is
  74.     the destination file, but it is taken to be a literal filename and is not
  75.     looked up in the database.)  (DESTLOOKUP)
  76. -l: Each file is looked up in the database, and the filename is printed
  77.     followed by the matches.  No conglomeration is done.
  78. -n: Force non-interactive mode: if multiple matches are found in database,
  79.     fail rather than offering them for selection.  (NONINTERACTIVE)
  80. -h: Print this help.
  81. -s: Save.  The source file is not removed.
  82. -t: Test.  The actions that would be performed are printed but not carried out.
  83. -v: Verbose.  The operations performed are printed.  (VERBOSE)"
  84.     exit 0
  85.     ;;
  86.     b)
  87.     backup=1
  88.     ;;
  89.     n)
  90.     interactive=0
  91.     ;;
  92.     d)
  93.     database=1
  94.     ;;
  95.     D)
  96.     destLookup=true
  97.     ;;
  98.     f)
  99.     DATABASE=$OPTARG;;
  100.     s)
  101.     remove=0
  102.     ;;
  103.     v)
  104.     verbose=1
  105.     ;;
  106.     l)
  107.     database=1
  108.     list=1
  109.     ;;
  110.     t)
  111.     test=true;;
  112.     +?)
  113.     print -u2 "$l_name: options should not be preceded by a '+'."
  114.     exit 1
  115.     ;;
  116.     :) 
  117.     print -r -u2 -- \
  118.     "$name: Option '$OPTARG' requires a value.  Use -h for help."
  119.     exit 1
  120.     ;;
  121.     ?) 
  122.     print -u2 "$l_name: $OPTARG: bad option.  Use -h for help."
  123.     exit 1
  124.     ;;
  125.     esac
  126. done
  127.  
  128. # Usage: ProcDest <destfile>
  129. # Process a destination file.  Check that it can be appended to,
  130. # and make a backup of it if backup is true.
  131. # Return nonzero on failure.
  132. # Globals used: backup verbose test
  133. function ProcDest {
  134.     typeset DestFile=$1
  135.     if [ ! -f "$DestFile" ]; then
  136.     print -r -u2 "$l_name: $DestFile: no such file."
  137.     return 1
  138.     fi
  139.     if [ ! -w "$DestFile" ]; then
  140.     print -r -u2 \
  141.     "$l_name: $DestFile: not writable."
  142.     return 1
  143.     fi
  144.     if istrue backup; then
  145.     if $test; then
  146.         print -r "Would do: cp $DestFile $DestFile-"
  147.     else
  148.         if cp "$DestFile" "$DestFile-"; then :; else
  149.         print -r -u2 "$l_name: $DestFile: could not copy to $DestFile-"
  150.         return 1
  151.         fi
  152.         istrue verbose && print -r -u2 "Copied $DestFile to $DestFile-"
  153.     fi
  154.     fi
  155.     return 0
  156. }
  157.  
  158. # Usage: FindMatch <filename>
  159. # Sets global FindMatch_ret to matching filename from database
  160. # Returns nonzero on error
  161. # Uses global vars db, HOME
  162. function FindMatch {
  163.     typeset file tail=${1##*/} ChkFile=$1 dest alldest DestFound file
  164.     typeset -i numdest=0
  165.  
  166.     OIFS=$IFS
  167.     IFS="
  168. "
  169.     for file in $(grep -e "$tail\$" "$db"); do
  170.     # grep may give extra files due to tail having e.g. '.' in it,
  171.     # and because we don't want to put / in front of $tail in the grep
  172.     # expression since bare files in home dir may be in database, nor
  173.     # do we want to use egrep since there would be even more opportunities
  174.     # for metacharacter misinterpretation...  so do a further test
  175.     if eval [[ \"$file\" = "?(*/)$tail" ]]; then
  176.         # Convert relative path in database to absolute path from home
  177.         [[ $file != /* ]] && dest="$HOME/${file#./}" || dest=$file
  178.         # Skip if it's the same file as the source file.
  179.         [[ "$ChkFile" -ef "$dest" ]] && continue
  180.         # Skip it if it isn't a regular file or doesn't exist anymore.
  181.         if [ ! -f "$dest" ]; then
  182.         [ ! -a "$dest" ] && print -r -u2 -- \
  183.         "$dest found in database, but no longer exists."
  184.         continue
  185.         fi
  186.         let numdest+=1
  187.         DestFound=$dest
  188.         alldest="$alldest
  189. $dest"
  190.     fi
  191.     done
  192.     file=$1
  193.     set -- $alldest
  194.     IFS=$OIFS
  195.     if istrue list; then
  196.     print -r -- "$file: $*"
  197.     return 0
  198.     fi
  199.     if isfalse numdest; then
  200.     print -r -u2 "$l_name: No matching file for $tail found in $db."
  201.     CloseFiles=
  202.     egrep -ie "(^|/)$tail(\\.z|\\.gz)?\$" "$db" | while read file; do
  203.         [[ $file != /* ]] && file="$HOME/$file"
  204.         [[ "$file" -ef "$dest" ]] || CloseFiles="$CloseFiles
  205. $file"
  206.     done
  207.     [ -n "$CloseFiles" ] && print -r -u2 "Note: did find:$CloseFiles"
  208.     return 1
  209.     fi
  210.     if [ numdest -gt 1 ]; then
  211.     if istrue interactive; then
  212.         print -r -u2 \
  213. "$numdest files ending in '$tail' found in $db
  214. Enter a number to select from the following:"
  215.         select file in $alldest "None of the above"; do
  216.         if [ -n "$file" ]; then
  217.             [ "$file" = "None of the above" ] && return 1 ||
  218.             FindMatch_ret=$file
  219.             return 0
  220.         fi
  221.         done
  222.         print -u2 "No selection."
  223.         return 1
  224.     else
  225.         print -r -u2 \
  226.         "$l_name: Found $numdest files ending in $tail in $db:$alldest"
  227.         return 1
  228.     fi
  229.     fi
  230.     FindMatch_ret=$DestFound
  231.     return 0
  232. }
  233.  
  234. # Usage: CheckDest <destfile>
  235. # Test destfile & set it up to have file(s) appended
  236. # Global DirDest is set to 1 if destination is a directory, 0 if a file
  237. # Globals used: DirDest remove
  238. # Return value is 0 for success, 1 for error
  239. function CheckDest {
  240.     typeset dest=$1
  241.     if [[ -d "$dest" || "$dest" = */ ]]; then
  242.     DirDest=1
  243.     if [ ! -d "$dest" ]; then
  244.         print -r -u2 "$l_name: $dest is not a directory."
  245.         return 1
  246.     fi
  247.     if [ ! -x "$dest" ]; then
  248.         print -r -u2 "$l_name: $dest: cannot access."
  249.         return 1
  250.     fi
  251.     else
  252.     DirDest=0
  253.     DestFile=$dest
  254.     if ProcDest "$DestFile"; then :; else
  255.         istrue remove && print -u2 "Source file(s) not removed."
  256.         return 1
  257.     fi
  258.     fi
  259.     return 0
  260. }
  261.  
  262. # Set getBase_ret to $1 without any compression suffix.
  263. # Sets getBase_suf to the compression suffix (without the leading dot), if any.
  264. # Return value: 0 if $1 had a compression suffix, else 1.
  265. function getBase {
  266.     getBase_ret=${1%.@(gz|z|Z)}
  267.     getBase_suf=${1#$getBase_ret}
  268.     [ "$1" = "$base" ]
  269. }
  270.  
  271. # Usage: Append sourcefile destfile
  272. # Uses globals: l_name test
  273. function Append {
  274.     typeset file=$1 DestFile=$2
  275.     typeset cat
  276.     if [ ! -f "$file" ]; then
  277.     print -r -u2 "$l_name: $file: no such file."
  278.     continue
  279.     fi
  280.     if [ ! -r "$file" ]; then
  281.     print -r -u2 "$l_name: $file is not readable."
  282.     continue
  283.     fi
  284.     case "$file" in
  285.     *.gz) cat=gunzip;;
  286.     *.Z)  cat=zcat;;
  287.     *.z)  cat=pcat;;
  288.     *)    cat=cat;;
  289.     esac
  290.     if "$test"; then
  291.     print -r -- "Would do: $cat < $file >> $DestFile"
  292.     return 0
  293.     fi
  294.     $cat < "$file" >> "$DestFile"
  295. }
  296.  
  297. # remove args that were options
  298. let OPTIND=OPTIND-1
  299. shift $OPTIND
  300.  
  301. if [ $# -eq 0 ]; then
  302.     print -u2 "$Usage\nUse -h for help."
  303.     exit 1
  304. fi
  305.  
  306. # Source file in UHOME first, so that values in HOME will have precedence.
  307. rc=$UHOME/$rcFile
  308. [ -f $rc -a -r $rc ] && . $rc
  309. rc=$HOME/$rcFile
  310. [ -f $rc -a -r $rc ] && . $rc
  311. [ -n "$BACKUP" ] && backup=1
  312. [ -n "$DESTLOOKUP" ] && destLookup=true
  313. [ -n "$NONINTERACTIVE" ] && interactive=0
  314. [ -n "$VERBOSE" ] && verbose=1
  315. # verbose messages are only to say that we did things that are not actually
  316. # done if test is true
  317. $test && verbose=0
  318.  
  319. typeset -i DirDest nsource numfiles=0 NumTried Success=0 Failed
  320.  
  321. if [ $# -eq 1 ]; then
  322.     database=1
  323. fi
  324.  
  325. [ -n "$DATABASE" ] && db=$DATABASE || db=$HOME/$db
  326.  
  327. # Find matches in database.  This block does not do the actual concatenation.
  328. if istrue database; then
  329.     if [ ! -f "$db" -o ! -r "$db" ]; then
  330.     print -r -u2 "$l_name: $db: cannot read."
  331.     exit 1
  332.     fi
  333.  
  334.     NumTried=$#
  335.     for file; do
  336.     getBase "$file"
  337.     if FindMatch "$getBase_ret"; then
  338.         if isfalse list && ProcDest "$FindMatch_ret"; then
  339.         if istrue DirDest; then
  340.             print -r -u2 \
  341. "$l_name: Destination $FindMatch_ret is a directory.
  342. Skipping source file $file."
  343.         else
  344.             SourceFiles[numfiles]=$file
  345.             DestFiles[numfiles]=$FindMatch_ret
  346.             let numfiles+=1
  347.         fi
  348.         fi
  349.     else
  350.         print -r -u2 "Skipping source file $file."
  351.     fi
  352.     done
  353. else
  354.     if [ $# -lt 2 ]; then
  355.     print -u2 "$Usage\nUse -h for help."
  356.     exit 1
  357.     fi
  358.     set -A args -- "$@"
  359.     nsource=$#-1
  360.     DestFile=${args[nsource]}
  361.     unset args[nsource]
  362.     if $destLookup && [[ "$DestFile" != */* ]]; then
  363.     FindMatch "$DestFile"
  364.     DestFile=$FindMatch_ret
  365.     fi
  366.     if CheckDest "$DestFile"; then :; else
  367.     print -u2 Aborting.
  368.     exit 1
  369.     fi
  370.     DestDir=$DestFile
  371.     NumTried=${#args[*]}
  372.     for file in "${args[@]}"; do
  373.     getBase "$file"
  374.     if istrue DirDest; then
  375.         DestFile="$DestDir/$getBase_ret"
  376.         ProcDest "$DestFile" || {
  377.         istrue remove && print -r -u2 \
  378.         "Appending of $file to $DestFile not attempted; $file not removed."
  379.         continue
  380.         }
  381.     fi
  382.     SourceFiles[numfiles]=$file
  383.     DestFiles[numfiles]=$DestFile
  384.     let numfiles+=1
  385.     done
  386. fi
  387.  
  388. istrue list && exit 0
  389.  
  390. typeset -i filenum=0
  391.  
  392. while [ filenum -lt numfiles ]; do
  393.     file=${SourceFiles[filenum]}
  394.     DestFile=${DestFiles[filenum]}
  395.     let filenum+=1
  396.     if Append "$file" "$DestFile"; then 
  397.     istrue verbose && print -r -u2 "Concatenated $file to $DestFile"
  398.     if istrue remove; then
  399.         if $test; then
  400.         print -r -- "Would do: rm -- $file"
  401.         let Success+=1
  402.         else
  403.         if rm -- "$file"; then
  404.             istrue verbose && print -r -u2 "Removed $file"
  405.             let Success+=1
  406.         else
  407.             print -r -u2 "$l_name: Failed to remove $file"
  408.         fi
  409.         fi
  410.     else
  411.         let Success+=1
  412.     fi
  413.     else
  414.     print -u2 "$l_name: concatenation failed."
  415.     istrue remove && print -r -u2 "Source file $file not removed."
  416.     fi
  417. done
  418.  
  419. Failed=NumTried-Success
  420. [ Failed -gt 255 ] && Failed=255
  421. exit $Failed
  422.